/**
 *
 */
package steven.location.service;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import steven.fusion.R;
import steven.graph.Edge;
import steven.ingress.AutopathController;
import steven.ingress.LocationNode;
import steven.location.activity.LocationActivity;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

/**
 * @author Steven
 *
 */
public class LocationService extends Service implements LocationListener{
	private enum Tab{
		ENABLE,
		MOCK,
		AUTOPATH;
	}

	private LinearLayout enablePanel;
	private LinearLayout mockPanel;
	private LinearLayout autopathPanel;
	private TextView passiveText;
	private Button upButton;
	private Button leftButton;
	private Button downButton;
	private Button rightButton;
	private LinearLayout row;
	private Button toggleMockMode;
	private Button speedButton;
	private Button autopathButton;
	private LinearLayout window;
	private WindowManager windowManager;
	private int windowX;
	private int windowY;
	private float windowTouchX;
	private float windowTouchY;
	private Tab currentTab;
	private LocationNode nearestNode;
	private LocationNode[] path;
	private long lastLocationTime;
	private double lastLatitude;
	private double lastLongitude;
	private Spinner autopathAreaSpinner;
	private Spinner autopathNodeSpinner;
	private CheckBox keepMovingCheckBox;
	private LinearLayout moveRow;
	private long averageLocationInterval;
	private double speed;
	private boolean windowMoved;
	private Button hideButton;
	private long hideTime;
	private LinearLayout leftPanel;

	@Override
	public IBinder onBind(final Intent intent){
		return null;
	}
	@Override
	public void onCreate(){
		super.onCreate();
		this.averageLocationInterval = 5000;
		this.lastLocationTime = System.currentTimeMillis() - this.averageLocationInterval;
		this.speed = 0.00001;
		this.createUI();
		final LocationManager locationManager = (LocationManager)super.getSystemService(Context.LOCATION_SERVICE);
		this.upButton.setText("^");
		this.upButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				LocationActivity.move(LocationService.this, 0, LocationService.this.getDistance());
			}
		});
		this.leftButton.setText("<");
		this.leftButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				LocationActivity.move(LocationService.this, -Math.PI / 2, LocationService.this.getDistance());
			}
		});
		this.downButton.setText("v");
		this.downButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				LocationActivity.move(LocationService.this, Math.PI, LocationService.this.getDistance());
			}
		});
		this.rightButton.setText(">");
		this.rightButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				LocationActivity.move(LocationService.this, Math.PI / 2, LocationService.this.getDistance());
			}
		});
		this.speedButton.setText("Walk");
		this.speedButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				if("Walk".equals(LocationService.this.speedButton.getText())){
					LocationService.this.speedButton.setText("Run");
					LocationService.this.speed = 0.00003;
				}else if("Run".equals(LocationService.this.speedButton.getText())){
					LocationService.this.speedButton.setText("Bus");
					LocationService.this.speed = 0.00006;
				}else if("Bus".equals(LocationService.this.speedButton.getText())){
					LocationService.this.speedButton.setText("Car");
					LocationService.this.speed = 0.00010;
				}else{
					LocationService.this.speedButton.setText("Walk");
					LocationService.this.speed = 0.00001;
				}
			}
		});
		if(locationManager.isMocking()){
			this.toggleMockMode.setText("disable");
			this.currentTab = Tab.MOCK;
		}else{
			this.toggleMockMode.setText("enable");
			this.currentTab = Tab.ENABLE;
		}
		this.toggleMockMode.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				final LocationManager locationManager = (LocationManager)LocationService.super.getSystemService(Context.LOCATION_SERVICE);
				if(locationManager.isMocking()){
					locationManager.setMocking(false);
					((Button)v).setText("enable");
					LocationService.this.path = null;
				}else{
					locationManager.setMocking(true);
					((Button)v).setText("disable");
					LocationService.this.currentTab = Tab.MOCK;
					LocationService.this.showTab();
					final Location location = locationManager.getLastGoodLocation();
					if(location != null){
						LocationActivity.setMockLocation((LocationManager)LocationService.this.getSystemService(Context.LOCATION_SERVICE), location.getLatitude(), location.getLongitude());
					}
				}
			}
		});
		this.autopathButton.setText("Go");
		this.autopathButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				if("Go".equals(LocationService.this.autopathButton.getText())){
					final LocationNode selected = LocationService.this.getSelectedNode();
					if(selected != null){
						LocationService.this.path = AutopathController.getPath(LocationService.this.lastLatitude, LocationService.this.lastLongitude, selected);
						if(LocationService.this.path != null && LocationService.this.path.length > 1){
							System.arraycopy(LocationService.this.path, 1, LocationService.this.path, 0, LocationService.this.path.length - 1);
							LocationService.this.updateAutopathButtonText();
						}else if(LocationService.this.path != null && LocationService.this.path.length > 0){
							LocationService.this.updateAutopathButtonText();
						}else{
							Toast.makeText(LocationService.this, "Invalid path.", Toast.LENGTH_SHORT).show();
						}
					}else{
						Toast.makeText(LocationService.this, "No node selected.", Toast.LENGTH_SHORT).show();
					}
				}else{
					LocationService.this.path = null;
					LocationService.this.autopathButton.setText("Go");
					LocationActivity.setMockLocation((LocationManager)LocationService.this.getSystemService(Context.LOCATION_SERVICE), LocationService.this.lastLatitude, LocationService.this.lastLongitude);
				}
			}
		});
		this.autopathAreaSpinner.setOnItemSelectedListener(new OnItemSelectedListener(){
			@Override
			public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id){
				LocationService.this.areaChanged();
			}
			@Override
			public void onNothingSelected(final AdapterView<?> parent){
			}
		});
		this.keepMovingCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
			@Override
			public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked){
				if(isChecked){
					LocationActivity.resetKeepMoving();
				}else{
					LocationService.this.upButton.setVisibility(View.VISIBLE);
					LocationService.this.row.setVisibility(View.VISIBLE);
					LocationActivity.setMockLocation((LocationManager)LocationService.this.getSystemService(Context.LOCATION_SERVICE), LocationService.this.lastLatitude, LocationService.this.lastLongitude);
				}
			}
		});
		this.hideButton.setText("hide");
		this.hideTime = Long.MAX_VALUE;
		this.hideButton.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(final View v){
				LocationService.this.window.removeAllViews();
				LocationService.this.hideTime = System.currentTimeMillis();
			}
		});
		this.showTab();
		// get screen size
		final DisplayMetrics dm = new DisplayMetrics();
		final Display display = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
		display.getMetrics(dm);
		locationManager.requestLocationUpdates(0, 0, new Criteria(), this, null);
		InputStream nodesJs = null;
		InputStream linksJs = null;
		try{
			nodesJs = super.getResources().openRawResource(R.raw.nodes);
			linksJs = super.getResources().openRawResource(R.raw.links);
			AutopathController.initialize(nodesJs, linksJs);
		}catch(final IOException e){
			e.printStackTrace();
			Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
		}finally{
			if(nodesJs != null){
				try{
					nodesJs.close();
				}catch(final IOException e){
				}
			}
			if(linksJs != null){
				try{
					linksJs.close();
				}catch(final IOException e){
				}
			}
		}
		this.initializeAutopathSpinner();
	}
	@Override
	public int onStartCommand(final Intent intent, final int flags, final int startId){
		return Service.START_NOT_STICKY;
	}
	@Override
	public void onDestroy(){
		this.windowManager.removeView(this.window);
		final LocationManager locationManager = (LocationManager)super.getSystemService(Context.LOCATION_SERVICE);
		locationManager.removeUpdates(this);
		super.onDestroy();
	}
	@Override
	public void onLocationChanged(final Location location){
		final long locationInterval = System.currentTimeMillis() - this.lastLocationTime;
		final LocationManager locationManager = (LocationManager)super.getSystemService(Context.LOCATION_SERVICE);
		if(locationManager.isMocking()){
			final Location mockLocation = locationManager.getMockLocation();
			final long t = (location.getTime() - mockLocation.getTime()) / 1000;
			final String ts;
			if(t > 1000){
				ts = (t / 1000) + "k";
			}else{
				ts = String.valueOf(t);
			}
			this.passiveText.setText(new SimpleDateFormat(":mm:ss", Locale.US).format(new Date(location.getTime())) + "\n" + ts);
		}else{
			this.passiveText.setText(new SimpleDateFormat(":mm:ss", Locale.US).format(new Date(location.getTime())));
		}
		boolean updatePosition = false;
		if(this.nearestNode == null){
			updatePosition = true;
		}else{
			final float[] distance = new float[1];
			Location.distanceBetween(this.lastLatitude, this.lastLongitude, location.getLatitude(), location.getLongitude(), distance);
			if(distance[0] > 10){
				updatePosition = true;
			}
		}
		this.lastLatitude = location.getLatitude();
		this.lastLongitude = location.getLongitude();
		if(updatePosition){
			this.nearestNode = AutopathController.getNearestNode(location.getLatitude(), location.getLongitude());
			this.areaChanged();
		}
		// keep / auto path
		if(this.path == null){
			if(this.keepMovingCheckBox.isChecked()){
				if(LocationActivity.keepMoving(this, this.getDistance())){
					this.upButton.setVisibility(View.GONE);
					this.row.setVisibility(View.GONE);
				}
			}
		}else{
			if(this.path.length == 0){
				this.path = null;
			}else{
				if(updatePosition){
					this.updateAutopathButtonText();
				}
				final double[] next = AutopathController.getNextPoint(this.path[0], location, this.getDistance());
				if(next == null){
					LocationActivity.setMockLocation(locationManager, this.path[0].getLatitude(), this.path[0].getLongitude());
					if(this.path.length > 1){
						final LocationNode[] newPath = new LocationNode[this.path.length - 1];
						System.arraycopy(this.path, 1, newPath, 0, newPath.length);
						this.path = newPath;
					}else{
						this.path = null;
						this.autopathButton.setText("Go");
					}
				}else{
					LocationActivity.setMockLocation(locationManager, next[0], next[1]);
				}
			}
		}
		this.averageLocationInterval = (long)(this.averageLocationInterval * 0.8 + locationInterval * 0.2);
		this.lastLocationTime = System.currentTimeMillis();
		if(this.lastLocationTime - this.hideTime > 60000){
			this.window.addView(this.leftPanel);
			this.window.addView(this.mockPanel);
			this.window.addView(this.enablePanel);
			this.window.addView(this.autopathPanel);
			this.hideTime = Long.MAX_VALUE;
		}
	}
	@Override
	public void onStatusChanged(final String provider, final int status, final Bundle extras){
	}
	@Override
	public void onProviderEnabled(final String provider){
	}
	@Override
	public void onProviderDisabled(final String provider){
	}
	private void createUI(){
		this.passiveText = new TextView(this);
		this.upButton = new Button(this);
		this.leftButton = new Button(this);
		this.downButton = new Button(this);
		this.rightButton = new Button(this);
		this.row = new LinearLayout(this);
		this.row.setOrientation(LinearLayout.HORIZONTAL);
		this.row.addView(this.leftButton, new LayoutParams(100, 100));
		this.row.addView(this.downButton, new LayoutParams(100, 100));
		this.row.addView(this.rightButton, new LayoutParams(100, 100));
		this.speedButton = new Button(this);
		this.keepMovingCheckBox = new CheckBox(this);
		this.keepMovingCheckBox.setBackgroundColor(Color.GRAY);
		this.moveRow = new LinearLayout(this);
		this.moveRow.setOrientation(LinearLayout.HORIZONTAL);
		this.moveRow.addView(this.keepMovingCheckBox);
		this.moveRow.addView(this.speedButton);
		this.mockPanel = new LinearLayout(this);
		this.mockPanel.setOrientation(LinearLayout.VERTICAL);
		this.mockPanel.addView(this.upButton, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
		this.mockPanel.addView(this.row, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
		this.mockPanel.addView(this.moveRow, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
		this.toggleMockMode = new Button(this);
		this.hideButton = new Button(this);
		this.enablePanel = new LinearLayout(this);
		this.enablePanel.setOrientation(LinearLayout.VERTICAL);
		this.enablePanel.addView(this.toggleMockMode, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
		this.enablePanel.addView(this.hideButton, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
		this.autopathButton = new Button(this);
		this.autopathPanel = new LinearLayout(this);
		this.autopathPanel.setOrientation(LinearLayout.VERTICAL);
		this.autopathAreaSpinner = new Spinner(this);
		this.autopathNodeSpinner = new Spinner(this);
		this.autopathPanel.addView(this.autopathAreaSpinner);
		this.autopathPanel.addView(this.autopathNodeSpinner);
		this.autopathPanel.addView(this.autopathButton, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100));
		// text size
		final int scale = 4;
		this.upButton.setTextSize(this.upButton.getTextSize() / scale);
		this.downButton.setTextSize(this.downButton.getTextSize() / scale);
		this.leftButton.setTextSize(this.leftButton.getTextSize() / scale);
		this.rightButton.setTextSize(this.rightButton.getTextSize() / scale);
		this.speedButton.setTextSize(this.speedButton.getTextSize() / scale);
		this.toggleMockMode.setTextSize(this.toggleMockMode.getTextSize() / scale);
		this.autopathButton.setTextSize(this.autopathButton.getTextSize() / scale);
		this.keepMovingCheckBox.setTextSize(this.keepMovingCheckBox.getTextSize() / scale);
		this.hideButton.setTextSize(this.hideButton.getTextSize() / scale);
		// window
		this.window = new LinearLayout(this);
		this.window.setOrientation(LinearLayout.HORIZONTAL);
		this.leftPanel = new LinearLayout(this);
		this.leftPanel.setOrientation(LinearLayout.VERTICAL);
		final ImageView image = new ImageView(this);
		image.setImageResource(R.drawable.ic_launcher);
		this.leftPanel.addView(image);
		this.leftPanel.addView(this.passiveText);
		this.window.addView(this.leftPanel);
		this.window.addView(this.mockPanel);
		this.window.addView(this.enablePanel);
		this.window.addView(this.autopathPanel);
		this.window.setOnTouchListener(new OnTouchListener(){
			@SuppressLint("ClickableViewAccessibility")
			@Override
			public boolean onTouch(final View v, final MotionEvent event){
				return LocationService.this.onWindowTouch((LinearLayout)v, event);
			}
		});
		this.windowManager = (WindowManager)super.getSystemService(Context.WINDOW_SERVICE);
		final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
		layoutParams.gravity = Gravity.TOP | Gravity.START;
		layoutParams.x = 0;
		layoutParams.y = 100;
		this.windowManager.addView(this.window, layoutParams);
	}
	private void showTab(){
		if(this.currentTab == Tab.ENABLE){
			this.enablePanel.setVisibility(View.VISIBLE);
			this.mockPanel.setVisibility(View.GONE);
			this.autopathPanel.setVisibility(View.GONE);
		}else if(this.currentTab == Tab.MOCK){
			this.enablePanel.setVisibility(View.GONE);
			this.mockPanel.setVisibility(View.VISIBLE);
			this.autopathPanel.setVisibility(View.GONE);
		}else if(this.currentTab == Tab.AUTOPATH){
			this.enablePanel.setVisibility(View.GONE);
			this.mockPanel.setVisibility(View.GONE);
			this.autopathPanel.setVisibility(View.VISIBLE);
		}
	}
	private void nextTab(){
		if(this.currentTab == Tab.ENABLE){
			final LocationManager locationManager = (LocationManager)super.getSystemService(Context.LOCATION_SERVICE);
			if(locationManager.isMocking()){
				this.currentTab = Tab.MOCK;
			}
		}else if(this.currentTab == Tab.MOCK){
			this.currentTab = Tab.AUTOPATH;
		}else if(this.currentTab == Tab.AUTOPATH){
			this.currentTab = Tab.ENABLE;
		}
	}
	private boolean onWindowTouch(final LinearLayout view, final MotionEvent event){
		switch(event.getAction()){
			case MotionEvent.ACTION_DOWN:
				this.windowX = ((WindowManager.LayoutParams)this.window.getLayoutParams()).x;
				this.windowY = ((WindowManager.LayoutParams)this.window.getLayoutParams()).y;
				this.windowTouchX = event.getRawX();
				this.windowTouchY = event.getRawY();
				this.windowMoved = false;
				return true;
			case MotionEvent.ACTION_UP:
				if(this.windowMoved == false){
					this.nextTab();
					this.showTab();
				}
				return true;
			case MotionEvent.ACTION_MOVE:
				((WindowManager.LayoutParams)this.window.getLayoutParams()).x = this.windowX + (int)(event.getRawX() - this.windowTouchX);
				((WindowManager.LayoutParams)this.window.getLayoutParams()).y = this.windowY + (int)(event.getRawY() - this.windowTouchY);
				this.windowManager.updateViewLayout(this.window, this.window.getLayoutParams());
				if(this.windowMoved == false && (Math.abs(this.windowTouchX - event.getRawX()) >= 10 || Math.abs(this.windowTouchY - event.getRawY()) >= 10)){
					this.windowMoved = true;
				}
				return true;
		}
		return false;
	}
	private void initializeAutopathSpinner(){
		final List<String> list = new ArrayList<String>();
		list.add("附近");
		for(final String area : AutopathController.getAreas()){
			list.add(area);
		}
		final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, list);
		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		this.autopathAreaSpinner.setAdapter(adapter);
		this.autopathAreaSpinner.setSelection(0);
	}
	private void areaChanged(){
		final String area = (String)this.autopathAreaSpinner.getItemAtPosition(this.autopathAreaSpinner.getSelectedItemPosition());
		final LocationNode previous = this.getSelectedNode();
		if(this.nearestNode != null){
			if("附近".equals(area)){
				final List<Edge<LocationNode>> list = new ArrayList<Edge<LocationNode>>();
				int d = this.nearestNode.distanceTo(this.lastLatitude, this.lastLongitude);
				if(d > 1000){
					d = -1;
				}
				list.add(new Edge<LocationNode>(this.nearestNode.getNode(), d));
				int selection = 0;
				for(final Edge<LocationNode> edge : AutopathController.getNearNodes(this.nearestNode, 1000, 10)){
					if(edge.getNode().getContent() == previous){
						selection = list.size();
					}
					list.add(edge);
				}
				final ArrayAdapter<Edge<LocationNode>> adapter = new ArrayAdapter<Edge<LocationNode>>(this, android.R.layout.simple_spinner_item, list);
				adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
				this.autopathNodeSpinner.setAdapter(adapter);
				this.autopathNodeSpinner.setSelection(selection);
			}else{
				final List<Edge<LocationNode>> list = new ArrayList<Edge<LocationNode>>();
				int selection = 0;
				for(final Edge<LocationNode> edge : AutopathController.getAreaNodes(area, this.lastLatitude, this.lastLongitude)){
					if(edge.getNode().getContent() == previous){
						selection = list.size();
					}
					list.add(edge);
				}
				final ArrayAdapter<Edge<LocationNode>> adapter = new ArrayAdapter<Edge<LocationNode>>(this, android.R.layout.simple_spinner_item, list);
				adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
				this.autopathNodeSpinner.setAdapter(adapter);
				this.autopathNodeSpinner.setSelection(selection);
			}
		}
	}
	@SuppressWarnings("unchecked")
	private LocationNode getSelectedNode(){
		if(this.autopathNodeSpinner.getAdapter() == null){
			return null;
		}else{
			return ((Edge<LocationNode>)this.autopathNodeSpinner.getItemAtPosition(this.autopathNodeSpinner.getSelectedItemPosition())).getNode().getContent();
		}
	}
	private void updateAutopathButtonText(){
		int distance = this.path[0].distanceTo(this.lastLatitude, this.lastLongitude);
		for(int i = 1; i < this.path.length; i++){
			final LocationNode next = this.path[i];
			for(final Edge<LocationNode> edge : this.path[i - 1].getNode().getEdges()){
				if(edge.getNode().getContent() == next){
					distance += edge.getDistance();
					break;
				}
			}
		}
		this.autopathButton.setText(this.path[this.path.length - 1].getName() + " " + distance + "m");
	}
	private double getDistance(){
		return this.averageLocationInterval * this.speed / 1000;
	}
}
